package a02;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

import util.Pair;

class Vier extends AbstractRegularGame<Byte> implements Serializable {

    private static final long serialVersionUID = 1L;

    final static byte[] possibleMoves = { 3, 2, 4, 1, 5, 0, 6 };

    final static byte[] startMoves = { 3, 2, 1, 0 };

    Vier() {
	super((byte) 7, (byte) 6);
    }

    @Override
    public int evalState(byte player) {
	return this.wins() ? this.lastPlayer() == player ? Integer.MAX_VALUE
		: -Integer.MAX_VALUE : this.state(player) - 10
		* this.state(this.otherPlayer(player));
    }

    @Override
    public List<Byte> moves() {
	final List<Byte> result = new LinkedList<Byte>();
	byte[] pms = this.getMovesDone() == 0 || this.getMovesDone() == 1
		&& this.getLastColumn() == 3 ? startMoves : possibleMoves;

	for (byte c : pms) {
	    if (this.getB()[c][0] == getNone()) {
		result.add(c);
	    }
	}
	return result;
    }

    @Override
    public boolean noMoreMove() {
	return this.getMovesDone() == this.getRows() * this.getColumns();
    }

    @Override
    public Vier setAtPosition(byte column, byte row) {
	return this.doMove(column);
    }

    @Override
    public boolean wins(byte p) {
	if (this.getMovesDone() < 7) {
	    return false;
	}
	if (this.getB()[this.getLastColumn()][this.getLastRow()] != p) {
	    return false;
	}
	return this.vertical(p) || this.horizontal(p) || this.diagonal1(p)
		|| this.diagonal2(p);
    }

    private boolean diagonal1(byte p) {
	byte inRow = 1;
	byte cc = (byte) (this.getLastColumn() - 1);
	byte cr = (byte) (this.getLastRow() - 1);
	while (cc >= 0 && cr >= 0 && this.getB()[cc][cr] == p) {
	    inRow++;
	    cc--;
	    cr--;
	}
	cc = (byte) (this.getLastColumn() + 1);
	cr = (byte) (this.getLastRow() + 1);
	while (cc < this.getColumns() && cr < this.getRows()
		&& this.getB()[cc][cr] == p) {
	    inRow++;
	    cc++;
	    cr++;
	}
	return inRow >= 4;
    }

    private boolean diagonal2(byte p) {
	byte inRow = 1;
	byte cc = (byte) (this.getLastColumn() - 1);
	byte cr = (byte) (this.getLastRow() + 1);
	while (cc >= 0 && cr < this.getRows() && this.getB()[cc][cr] == p) {
	    inRow++;
	    cc--;
	    cr++;
	}
	cc = (byte) (this.getLastColumn() + 1);
	cr = (byte) (this.getLastRow() - 1);
	while (cc < this.getColumns() && cr >= 0 && this.getB()[cc][cr] == p) {
	    inRow++;
	    cc++;
	    cr--;
	}
	return inRow >= 4;
    }

    private boolean horizontal(byte p) {
	byte inRow = 1;
	for (int cc = this.getLastColumn() - 1; cc >= 0
		&& this.getB()[cc][this.getLastRow()] == p; cc--) {
	    inRow++;
	}
	for (int cc = this.getLastColumn() + 1; cc < this.getColumns()
		&& this.getB()[cc][this.getLastRow()] == p; cc++) {
	    inRow++;
	}
	return inRow >= 4;
    }

    private boolean vertical(byte p) {
	byte inRow = 1;
	for (int cr = this.getLastRow() + 1; cr < this.getRows()
		&& this.getB()[this.getLastColumn()][cr] == p; cr++) {
	    inRow++;
	}
	return inRow >= 4;
    }

    private List<Pair<Byte, Byte>> getListDiagonal1(byte p) {
	List<Pair<Byte, Byte>> winningStones = new LinkedList<Pair<Byte, Byte>>();
	winningStones.add(new Pair<Byte,Byte>(this.getLastColumn(),this.getLastRow()));	
	byte cc = (byte) (this.getLastColumn() - 1);
	byte cr = (byte) (this.getLastRow() - 1);
	while (cc >= 0 && cr >= 0 && this.getB()[cc][cr] == p) {
	    winningStones.add(new Pair<Byte, Byte>(cc, cr));
	    cc--;
	    cr--;
	}
	cc = (byte) (this.getLastColumn() + 1);
	cr = (byte) (this.getLastRow() + 1);
	while (cc < this.getColumns() && cr < this.getRows()
		&& this.getB()[cc][cr] == p) {
	    winningStones.add(new Pair<Byte, Byte>(cc, cr));
	    cc++;
	    cr++;
	}
	return winningStones;
    }

    private List<Pair<Byte, Byte>> getListDiagonal2(byte p) {
	List<Pair<Byte, Byte>> winningStones = new LinkedList<Pair<Byte, Byte>>();
	winningStones.add(new Pair<Byte,Byte>(this.getLastColumn(),this.getLastRow()));		
	byte cc = (byte) (this.getLastColumn() - 1);
	byte cr = (byte) (this.getLastRow() + 1);
	while (cc >= 0 && cr < this.getRows() && this.getB()[cc][cr] == p) {
	    winningStones.add(new Pair<Byte, Byte>(cc, cr));
	    cc--;
	    cr++;
	}
	cc = (byte) (this.getLastColumn() + 1);
	cr = (byte) (this.getLastRow() - 1);
	while (cc < this.getColumns() && cr >= 0 && this.getB()[cc][cr] == p) {
	    winningStones.add(new Pair<Byte, Byte>(cc, cr));
	    cc++;
	    cr--;
	}
	return winningStones;
    }

    private List<Pair<Byte, Byte>> getListHorizontal(byte p) {
	List<Pair<Byte, Byte>> winningStones = new LinkedList<Pair<Byte, Byte>>();
	winningStones.add(new Pair<Byte,Byte>(this.getLastColumn(),this.getLastRow()));	
	for (int cc = this.getLastColumn() - 1; cc >= 0
		&& this.getB()[cc][this.getLastRow()] == p; cc--) {
	    winningStones
		    .add(new Pair<Byte, Byte>((byte) cc, this.getLastRow()));
	}
	for (int cc = this.getLastColumn() + 1; cc < this.getColumns()
		&& this.getB()[cc][this.getLastRow()] == p; cc++) {
	    winningStones
		    .add(new Pair<Byte, Byte>((byte) cc, this.getLastRow()));
	}
	return winningStones;
    }

    private List<Pair<Byte, Byte>> getListVertical(byte p) {
	List<Pair<Byte, Byte>> winningStones = new LinkedList<Pair<Byte, Byte>>();
	winningStones.add(new Pair<Byte,Byte>(this.getLastColumn(),this.getLastRow()));	
	for (int cr = this.getLastRow() + 1; cr < this.getRows()
		&& this.getB()[this.getLastColumn()][cr] == p; cr++) {
	    winningStones.add(new Pair<Byte, Byte>(this.getLastColumn(),
		    (byte) cr));	    
	}

	return winningStones;
    }

    public int ps(byte player, int rV) {
	return rV == 3 * player ? 100 : rV == 2 * player ? 10 : 0;
    }

    public int state(byte p) {
	int result = 0;
	for (int c = 0; c < (this.getColumns()-1) - 3; c++) {
	    for (int r = this.getRows() - 1; r > 0 + 3; r--) {
		result = result
			+ this.ps(p, this.getB()[c][r] + this.getB()[c][r - 1]
				+ this.getB()[c][r - 2] + this.getB()[c][r - 3])
			+ this.ps(p, this.getB()[c][r]
				+ this.getB()[c + 1][r - 1]
				+ this.getB()[c + 2][r - 2]
				+ this.getB()[c + 3][r - 3])
			+ this.ps(p, this.getB()[c][r] + this.getB()[c + 1][r]
				+ this.getB()[c + 2][r] + this.getB()[c + 3][r]);

		final int c2 = this.getColumns() - 1 - c;
		if (c2 > 3) {
		    result = result
			    + this.ps(p, this.getB()[c2][r]
				    + this.getB()[c2][r - 1]
				    + this.getB()[c2][r - 2]
				    + this.getB()[c2][r - 3]);
		}

		final int s = 0 + r;
		result = result
			+ this.ps(p, this.getB()[c][s] + this.getB()[c + 1][s]
				+ this.getB()[c + 2][s] + this.getB()[c + 3][s])
			+ this.ps(p, this.getB()[c][s]
				+ this.getB()[c + 1][s - 1]
				+ this.getB()[c + 2][s - 2]
				+ this.getB()[c + 3][s - 3]);
	    }
	}
	return result;
    }

    @Override
    public Vier doMove(Byte c) {
	Vier result = (Vier) this.clone();

	for (byte r = (byte) (this.getRows() - 1); r > -1; r--) {
	    if (result.getB()[c][r] == getNone()) {
		result.getB()[c][r] = this.getPlayer();
		result.setPlayer(this.nextPlayer());
		result.setLastColumn(c);
		result.setLastRow(r);
		result.setMovesDone(this.getMovesDone() + 1);
		break;
	    }
	}

	return result;
    }

    public Vier reset() {
	return new Vier();
    }

    public List<Pair<Byte, Byte>> getWinningLine() {
	List<Pair<Byte, Byte>> winningStones = new LinkedList<Pair<Byte, Byte>>();
	if (vertical(this.nextPlayer())) {
	    winningStones = getListVertical(this.nextPlayer());
	} else if (horizontal(this.nextPlayer())) {
	    winningStones = getListHorizontal(this.nextPlayer());
	} else if (diagonal1(this.nextPlayer())) {
	    winningStones = getListDiagonal1(this.nextPlayer());
	} else if (diagonal2(this.nextPlayer())) {
	    winningStones = getListDiagonal2(this.nextPlayer());	
	}
	return winningStones;
    }

}
